<?php

class gamesocket
{
	var $socket		= NULL;
	var $magic		= array();
	var $version	= array();
	var $serialno	= array();
	var $p_type 	= array();
	var $checksum	= array();
	var $flag		= array();					
	var $format		= 'ccccccccclcccccc';		//pack打包通用头部消息的参数顺序
	var $connectType = 2;						//初始化默认为连接gatewaymgr

	/**
	 * 构造函数
	 * @param 连接类型 $connectType		1--dbmgr,2--gatewaymgr
	 */
	function __construct($connectType = 2)
	{
		$this->gamesocket($connectType);
	}

	/**
	 * 初始化，连接socket
	 * @param 连接类型 $connectType	1--dbmgr,2--gatewaymgr
	 */
	function gamesocket($connectType)
	{
		$this->connectType = $connectType;										//连接类型
		$this->gamesocketConnect($this->connectType);							//创建socket
		
	}

	/**
	 * 连接服务端socket 
	 * @param $connectType 连接类型 	
	 * 		1--dbmgr,2--gatewaymgr
	 */
	function gamesocketConnect($connectType)
	{
		global $socket_dbmgr, $socket_gateway;
		$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die($this->socketerror());		//连接socket
			
		if($connectType == 1)				// 连接服务端dbmgr
		{								
			$connection = socket_connect($this->socket, $socket_dbmgr['address'],$socket_dbmgr['port']);
			if(!$connection)				//socket连接失败
			{
				header('Content-Type: text/html; charset=gbk');
				echo $this->socketerror();	//输出失败信息
				echo "服务器已经关闭!";
				exit;
			}
		}
		elseif($connectType == 2)			// 连接服务端gatewaymgr
		{							
			$connection = socket_connect($this->socket, $socket_gateway['address'],$socket_gateway['port']);
			if(!$connection)				//socket连接失败
			{
				header('Content-Type: text/html; charset=gbk');
				echo $this->socketerror();	//输出失败信息
				echo 'gatewary:'.$socket_gateway['address'].':'.$socket_gateway['port'];
				exit;
			}
		}
	}
	
	/**
	 * 数据包头部打包
	 * typedef struct _t_header
		{
		  uint32 magic;		//包头标识，统一为”FC0x070xC0”,协议版本，高4位标识为高位版本号，低4位标识为低位版本号,如版本1.0，记录为0x10，2.12则记录为0x2C
		  uint8  version;
		  uint32 serialno;
		  uint32 type;     协议号
		  uint32 chekcsum;
		  uint16 flag;
		  size_t size;		//UINT8 | UINT8 UINT16当元素的大小小于255字节，则size占用一个字节，并记录真实长度。
		  					当元素的大小大于或等于255个字节，则size的第一个字节记录为255，后两个字节记录元素的长度（255~65535）
		}header;
	 * @param $protocol
	 */
	function packHeader($protocol)
	{
		$this->magic 	= array(0x46,0x43,0x07,0xC0);
		$this->version 	= array(0x00);
		$this->serialno = array(0x00,0x00,0x00,0x00);
		$this->protocol = array(													//服务端提供的协议号
								'createSession'			=>	array(0x00010007),			//登陆平台
								'email'					=>	array(0x0002002B),			//群发邮件
								'singleReward'			=>	array(0x0002002C),			//单人奖励
								'cycleNotice'			=>	array(0x00020034),			//发布循环公告
								'delCycleNotice'		=>	array(0x00020035),			//删除循环公告
								'closeGame'				=>	array(0x00020060),			//关服公告
								'noLogin'				=>	array(0x0002002E),			//封号
								'noMsg'					=>	array(0x0002002F),			//禁言
								'noGuide'				=>	array(0x00020040),			//新手指导
								'queryPlayer'			=>	array(0x00010033),			//查询玩家昵称
								'fastdb'				=>	array(0x0000100FE),			//操作内存数据库
								'charge'				=>	array(0x0002003E),			//玩家充值
								'greenHand'				=>	array(0x0001002B),			//新手礼包
								'qPointSendItem'		=>	array(0x00020065),			//Q点购买物品发货
								'flushQPoint'			=>	array(0x00020066),			//刷新玩家Q点余额
								'qPointReturnParam'		=>	array(0x00020067),			//Q点购买物品返回url_param参数给flash调用JS
								'phpErrorCode'			=>	array(0x00020068),			//返回错误码协议
								'addIp'					=> 	array(0x00020069),			//新增禁止IP
								'deleteIp'				=>	array(0x00020070),			//删除禁止IP
								'queryIp'				=>  array(0x00020071),			//查询禁止IP
								'marquee'				=>  array(0x00020029),			//走马灯公告
								'reloaddbstatic'		=>  array(0x0000100FF),			//重载静态数据
								'addactivity' 			=>  array(0x00020092),			//开始活动
								'delactivity' 			=>  array(0x00020093),			//停止活动
						 );
		$this->checksum = array(0x00,0x00,0x00,0x00);
		$this->flag 	= array(0x00,0x00);
		
		$header = array_merge($this->magic,$this->version,$this->serialno,$this->protocol[$protocol],$this->checksum,$this->flag);
		$msg['buffer']=null;
		$i = 0;
		foreach ($header as $val)							//头部打包
		{
			$msg['buffer'].=pack($this->format[$i],$val);
			$i++;
		}
	}
	
	/**
	 * 获取整个包的size
	 * @param $send_data
	 */
	function getTotalSize($send_data)
	{
		$size = 0;	
		foreach($send_data as $key => $val)					//获取每种数据类型长度,汇总成要发送数据包的总长度
		{
			switch($val['type'])
			{
				case 1:										//uint8
					$size += 1;
					break;
				case 2:										//uint32
					$size += 4;
					break;
				case 3:										//std::string
					$length = strlen($val['data']);
					if($length < 255)
					{
						$size += (1+$length);
					}
					else
					{
						$size += (3+$length);
					}
					break;
				case 4:										//std::vector<key_value>
					$length = count($val['data'])*6;
					$size += ($length +1);
					break;
				case 5:											//std::vector<uint32>
					$length = count($val['data'])*4;
					$size += ($length + 1);
					break;
				case 6:										//std::vector<string>
					foreach($val['data'] as $v)
					{
						$length += strlen($v);
					}
					$size += ($length + 1 + count($val['data']));
					break;
				case 7:										//uint16
					$size += 2;
					break;
					
				case 8:										//std::vector<vector<string,string>>
					foreach ($val['data'] as $k=>$v)
					{
						$length = 0;
						foreach($v as $k1=>$v1)				
						{
							$length += strlen($v1);			//每个url对字符本身的总长度
						}
						$size += $length + count($v);		//整个url对总长度
					}
					$size += 1;								//所有url对的总长度
					break;
			}
		}
		return $size;
	}
	
	/**
	 * 将发送数据打包
	 * @param array $send_data
	 * @param string $protocol
	 * return string msg 返回按格式顺序打包好的数据包
	 */
	function packmsg($send_data,$protocol)
	{
//		$header = array_merge($this->magic,$this->version,$this->serialno,$this->protocol[$protocol],$this->checksum,$this->flag);
//		$msg['buffer']=null;
//		$i = 0;
//		foreach ($header as $val)							//头部打包
//		{
//			$msg['buffer'].=pack($this->format[$i],$val);
//			$i++;
//		}

//		$size = 0;											//这个包的长度:头部+数据
//		foreach($send_data as $key => $val)					//获取每种数据类型长度,汇总成要发送数据包的总长度
//		{
//			switch($val['type'])
//			{
//				case 1:										//uint8
//					$size += 1;
//					break;
//				case 2:										//uint32
//					$size += 4;
//					break;
//				case 3:										//std::string
//					$length = strlen($val['data']);
//					if($length < 255)
//					{
//						$size += (1+$length);
//					}
//					else
//					{
//						$size += (3+$length);
//					}
//					break;
//				case 4:										//std::vector<key_value>
//					$length = count($val['data'])*6;
//					$size += ($length +1);
//					break;
//				case 5:											//std::vector<uint32>
//					$length = count($val['data'])*4;
//					$size += ($length + 1);
//					break;
//				case 6:										//std::vector<string>
//					foreach($val['data'] as $v)
//					{
//						$length += strlen($v);
//					}
//					$size += ($length + 1 + count($val['data']));
//					break;
//				case 7:										//uint16
//					$size += 2;
//					break;
//					
//				case 8:										//std::vector<vector<string,string>>
//					foreach ($val['data'] as $k=>$v)
//					{
//						$length = 0;
//						foreach($v as $k1=>$v1)				
//						{
//							$length += strlen($v1);			//每个url对字符本身的总长度
//						}
//						$size += $length + count($v);		//整个url对总长度
//					}
//					$size += 1;								//所有url对的总长度
//					break;
//			}
//		}

		if($size <255)								//size的算法，当元素的大小小于255字节，则size占用一个字节，并记录真实长度。	 uint8  sizeflag;uint16 datasize;
		{
			$msg['buffer'] .= pack('c*',$size);
		}
		else										//当元素的大小大于或等于255个字节，则size的第一个字节记录为255，后两个字节记录元素的长度（255~65535）
		{
			$msg['buffer'] .= pack('c*',255);
			$msg['buffer'] .= pack('s*',$size);		//注意高低字节顺序
		}

		foreach($send_data as $key=>$val)									//根据数据类型依次打包
		{
			switch($val['type'])
			{
				case 1:														//uint8
					$msg['buffer'] .= pack('c*',$val['data']);
					break;
				case 2:														//uint32
					$msg['buffer'] .= pack('L*',$val['data']);
					break;
				case 3:														//std::string
					if(strlen($val['data'])<255)
					{
						$msg['buffer'] .= pack('c*',strlen($val['data']));		//长度打包
					}
					else
					{
						$msg['buffer'] .= pack('c*',255);						//第一个字节用255打包,后面两个字节打包实际数据
						$msg['buffer'] .= pack('s*',strlen($val['data']));		//注意高低字节顺序
					}
					$msg['buffer'] .= pack('a*',$val['data']);					//数据本身打包
					break;
				case 4:														//std::vector<key_value>
					$msg['buffer'] .= pack('c*',count($val['data']));
					if($val['data'])
					{
						foreach($val['data'] as $v)
						{
							$msg['buffer'] .= pack('S*',$v['key']);
							$msg['buffer'] .= pack('L*',$v['value']);
						}
					}
					break;
				case 5:														//std::vector<uint32>
					$msg['buffer'] .= pack('c*',count($val['data']));
					foreach($val['data'] as $v)
					{
						$msg['buffer'] .= pack('L*',$v);
					}
					break;
				case 6:														//std::vector<string>
					$msg['buffer'] .= pack('c*',count($val['data']));
					foreach($val['data'] as $v)
					{
						$length = strlen($v);
						$msg['buffer'] .= pack('c*',$length);
						$msg['buffer'] .= pack('a*',$v);
					}
					break;
				case 7:														//uint16
					$msg['buffer'] .= pack('S*',$val['data']);
					break;
					
				case 8:
					$msg['buffer'] .= pack('c*',count($val['data']));		//大于255时也要用3个字节存储
					foreach($val['data'] as $k=>$v)
					{
						foreach($v as $k1=>$v1)
						{
							$length = strlen($v1);
							if($length < 255)
							{
								$msg['buffer'] .= pack('c*',$length);
							}
							else
							{
								$msg['buffer'] .= pack('c*',255);						//第一个字节用255打包,后面两个字节打包实际数据
								$msg['buffer'] .= pack('s*',$length);					//注意高低字节顺序
							}
							$msg['buffer'] .= pack('a*',$v1);							//数据本身打包
						}
					}
					break;
			}
		}
		return $msg['buffer'];
	}

	
	function packBasic()
	{
		
	}
	
	/**
	 * 发送数据
	 * @param $data
	 * @param $type
	 */
	function send($data,$protocol)
	{
		if(!$this->socket)
		{
			$this->connect();
		}

		$msg = $this->packmsg($data,$protocol);			//数据打包
		$is_send = socket_send($this->socket, $msg, strlen($msg), 0);
		if($is_send)									//已发送
		{
			header('Content-Type: text/html; charset=utf-8');
			$returnData = $this->receive($this->socket,$protocol);		//服务端返回成功数据，通信成功
			return $returnData;
		}
		else 											//发送失败
		{
			echo $this->socketerror();
			exit;
		}
	}

	/**
	 * 接受服务端返回数据
	 * @param $socket
	 * @param $protocol
	 */
	function receive($socket,$protocol = NULL)
	{
		socket_recv($socket, $buf, 2048, 0) or die($this->socketerror());
		$receiveData = $buf;							//返回数据存入缓冲区
		$this->close();									//关闭socket

		//根据协议解包不同的服务端应答包
		//应答包格式header commonhead;uint32 retcode;
		if($protocol == 'fastdb')						//fastdb操作
		{
			 $receiveInfo = unpack('H8magic/H2version/H8serialno/H8type/H8checksum/H4flag/H2size_flag/h8retcode/a*data', $receiveData);
		}elseif ($protocol == 'createSession'){	//createsession
			 $receiveInfo = unpack('H8magic/H2version/H8serialno/H8type/H8checksum/H4flag/H2size_flag/h8retcode/Luser_id', $receiveData);
		}elseif ($protocol == 'queryPlayer'){		//查询用户昵称
			$receiveInfo = unpack('H8magic/H2version/H8serialno/H8type/H8checksum/H4flag/H2size_flag/h8retcode/C1len/a*nickname', $receiveData);
		}elseif ($protocol == 'greenHand'){			//新手礼包
			$receiveInfo = unpack('H8magic/H2version/H8serialno/H8type/H8checksum/H4flag/H2size_flag/h8retcode/C1len/a*num', $receiveData);
		}elseif($protocol == 'closeGame'){				//关服公告
			$receiveInfo = unpack('H8magic/H2version/H8serialno/H8type/H8checksum/H4flag/H2size_flag/H8retcode/H8time', $receiveData);			
		}elseif($protocol == 'addIp'){				//增加封禁ip
			$receiveInfo = unpack('H8magic/H2version/H8serialno/H8type/H8checksum/H4flag/H2size_flag/h8retcode/LipId', $receiveData);
		}elseif($protocol == 'queryIp'){			//查询封禁ip
			$receiveInfo = unpack('H8magic/H2version/H8serialno/H8type/H8checksum/H4flag/H2size_flag/a*data', $receiveData);
			if(hexdec($receiveInfo['size_flag']) == 255)		//头部字节大于255
			{
				$receiveInfo = unpack('H4header_size/a*data', $receiveInfo['data']);
			}
			$receiveInfo = unpack('h8retcode/a*data', $receiveInfo['data']);	
			$tmp = unpack('cipnum/a*data',$receiveInfo['data']);
			$ipNum = $tmp['ipnum'];
			$ipIdArray = array();
			while($ipNum--)
			{
				$tmp = unpack('Lipid/a*data', $tmp['data']);
				$ipIdArray[] = $tmp['ipid'];
			}
			$tmp = unpack('cipnum/a*data', $tmp['data']);
			$ipNum = $tmp['ipnum'];
			$ipvalueArray = array();
			while($ipNum--)
			{
				$tmp = unpack('clen/a*data',$tmp['data']);
				$len = $tmp['len'];
				$tmp = unpack('a'.$len.'ipvalue/a*data',$tmp['data']);
				$ipvalueArray[] = $tmp['ipvalue'];
			}
			$receiveInfo['ipIdArray'] = $ipIdArray;
			$receiveInfo['ipvalueArray'] = $ipvalueArray; 
		}	
		else 										//其他协议
		{
			$receiveInfo = unpack('H8magic/H2version/H8serialno/H8type/H8checksum/H4flag/H2size_flag/h8retcode', $receiveData);
		}
		$receiveInfo['retcode'] = strrev($receiveInfo['retcode']);
		return $receiveInfo;
	}

	/**
	 * 关闭连接
	 */
	function close()
	{
		socket_close($this->socket);
	}

	/**
	 * 获取错误信息
	 */
	function socketerror()
	{
		return socket_strerror(socket_last_error());
	}
	
	/**
	 * 当长度大于255时，用3个字节打包
	 * @param $len
	 */
	function packBigLength($len)
	{
		if($len < 255)
		{
			$msg['buffer'] .= pack('c*',$len);
		}
		else
		{
			$msg['buffer'] .= pack('c*',255);						//第一个字节用255打包,后面两个字节打包实际数据
			$msg['buffer'] .= pack('s*',$len);						//注意高低字节顺序
		}
		return $msg['buffer'];
	}
	
	
}